/****************************************
2023年12月10日
需要注释请查看DHT11-note.ino
技术支持qq1836035711
****************************************/

#define BLINKER_WIFI

//#define RELAYPIN_1 0  //定义继电器1在8266芯片的引脚GPIO0，对应NodeMCU开发板D3
//#define RELAYPIN_2 2  //定义继电器2在8266芯片的引脚GPIO2，对应NodeMCU开发板D4

//#define DHTPIN 4      //定义DHT11 在8266芯片的引脚GPIO4，对应NodeMCU开发板D2
#define DHTPIN 26    //定义DHT11 在8266芯片的引脚GPIO2，对应NodeMCU开发板D4
//#define APPTEMP     //不接DHT11，从APP调试框或输入框赋值测试
//#define RANTEMP     //不接DHT11，随机数测试 

//#define SELF_LOCK     //焊接自复位按钮注释掉本句，焊接自锁开关取消注释
//#define BUTTONPIN 2 //实体开关，定义后检测IO2，低电平则调用按钮1回调翻转一次灯状态


#define DHTTYPE DHT11

//4.必须填写！定义心知天气API接口地址，否则容易崩溃(URI，通用资源标志符，Universal Resource Identifier)
// (1)必须填写！key注册心知天气获取免费版后私钥贴到ABCDEFG(""引号不要去掉！)
#define weather_key "SrCYaNtEMIZHjABt5"
// (2)必须填写！location=查看本项目附带的心知天气城市列表以填写“城市拼音”或“城市代码”到WTTDPCGXTWUS(免费版只精确到地级市！！！不要写区县代码！！！)
#define weather_location "WTSQQYHVQ973"

#include <Blinker.h> 
#include <DHT.h>   

// 定义设备密钥、WiFi名称和密码
char auth[] = "b1d99413affa";
char ssid[] = "xlt";
char pswd[] = "12345678";

String data="";              
String date="";            
String tip="";             
String weather_text="";     
String weather_icon="";   
String temperature="";       
String sig_str="",sign_icon="";

uint32_t weat_time = 0;      
uint32_t read_time = 0;    
uint32_t stor_time = 0;     
uint32_t icon_time = 0;     
uint32_t offline_millis = 0; 
uint32_t tap_time=0;      

float hic, hif;               
float humi_read,temp_read,temp_read_f;
float temp_APP;           

bool weather_success=false;  
bool offline_flag=false;      
bool button1_state=false;   
bool button2_state=false;   

uint32_t hebt_time = 0;
uint32_t hebt_time_limit = 0;

float water_read;//

BlinkerText Text1("info");    
BlinkerText Text2("runtime"); 
BlinkerText Text3("calendar");
BlinkerText Text4("weather"); 
BlinkerNumber HUMI("humi");   
BlinkerNumber TEMP("temp");   
BlinkerNumber TEMPF("tempf"); 
BlinkerNumber HIC("hic");    
BlinkerNumber HIF("hif");    
#if defined(DHTPIN)
DHT dht(DHTPIN, DHTTYPE);    
#endif

int cp=0;//chepai

BlinkerNumber WATER("water"); // 水位监测组件
BlinkerText Text5("text5"); 
void signal_read()//0.WiFi信号强度读取函数
{   
    int signal=WiFi.RSSI();
    sig_str="信号强度:";
    sig_str+=signal;
    sig_str+="dBm，";    
    sig_str+="信号等级:";sign_icon="";
         if(signal>-200&&signal<=-100){sig_str+="极弱"; sign_icon+="fad fa-signal-1";}
    else if(signal>-100&&signal<=-80){sig_str+="很弱"; sign_icon+="fad fa-signal-2";}
    else if(signal>-80&&signal<=-70){sig_str+="较弱"; sign_icon+="fad fa-signal-3";}
    else if(signal>-70&&signal<=-50){sig_str+="正常"; sign_icon+="fad fa-signal-4";}
    else if(signal>-50&&signal<=0){sig_str+="极强"; sign_icon+="fad fa-signal";}
    else                          {sig_str+="需要硬件重启"; sign_icon+="fad fa-signal-slash";}
}
String run_time()
{
    //声明——局部变量
    int rt =Blinker.runTime();
    int r,e,f,s;              
    String fh;              
    
    if(rt >= 86400)                     
    {
         r = rt / 86400;                 
         e = rt / 3600 - r*24;            
         f = rt / 60 - r*1440 - e*60;    
         s = rt - r*86400 - e*3600 - f*60; 
    }
    else if(rt >= 3600)                
    {
         r = 0;
         e = rt / 3600;
         f = rt / 60 - e*60;
         s = rt - e*3600 - f*60;
    }
    else                                  
    {
         r = 0;
         e = 0;
         f = rt / 60;
         s = rt - f*60;
    }
    //BLINKER_LOG(r," ",e," ",f," ",s);                           
         if(f==0 & e==0 & r==0)fh = String("")+ s +"秒";           
    else if(r == 0 & e == 0 )fh = String("")+ f + "分" + s +"秒";  
    else if(r == 0)fh = String("")+ e + "时" + f + "分" + s +"秒"; 
    else fh = String("")+ r + "天" + e + "时" + f + "分" + s +"秒";
    return(fh);                                                
    }    
void date_read (void)
{   
    int8_t sec = Blinker.second(); 
    int8_t min = Blinker.minute();
    int8_t hour = Blinker.hour();  
    int8_t wday = Blinker.wday(); 
    int8_t mday = Blinker.mday(); 
    int16_t yday = Blinker.yday();
    int8_t month = Blinker.month();
    int16_t year = Blinker.year(); 
   
    String AMPM="";                
    if(hour==-1)AMPM="  ";         
    else if(hour==0){hour=12;             AMPM="半夜";}
    else if(hour>0&&hour<=5)              AMPM="凌晨";
    else if(hour>=6&&hour<=10)            AMPM="上午";
    else if(hour==11||hour==12)           AMPM="中午";
    else if(hour>=13||hour<=17){hour-=12; AMPM="下午";}
    else if(hour>=18&&hour<=19){hour-=12; AMPM="傍晚";}
    else if(hour>=20&&hour<=23){hour-=12; AMPM="晚上";}
    
    String zhwday="";              
    if(wday==-1)zhwday="";if(wday==0)zhwday="日";if(wday==1)zhwday="一";if(wday==2)zhwday="二";
    if(wday==3)zhwday="三";if(wday==4)zhwday="四";if(wday==5)zhwday="五";if(wday==6)zhwday="六";

    int8_t ydaypercent = 100*yday/365;       int8_t mdaypercent = 100*mday/31; 
   
    date="";date+=year;date+="年";date+=month;date+="月";date+=mday;date+="日";date+=" 星期";date+=zhwday;date+=" ";date+=AMPM;date+=hour;date+="点";date+=min;date+="分";date+=sec;date+="秒"; BLINKER_LOG_ALL(date);
    tip="";tip+="今年已过去";tip+=yday;tip+="天(";tip+=ydaypercent;tip+="%)"; tip+="本月已过去";tip+=mday;tip+="天(";tip+=mdaypercent;tip+="%)";BLINKER_LOG_ALL(tip);
}
void weather_read(void)
{ 
    String XINZHI_uri="http://api.seniverse.com/v3/weather/now.json?key=";
    XINZHI_uri+=weather_key;
    XINZHI_uri+="&location=";
    XINZHI_uri+=weather_location;
    XINZHI_uri+="&language=zh-Hans&unit=c";

    WiFiClient client;              
    HTTPClient http;                  
    http.begin(client,XINZHI_uri);   
    int httpGet = http.GET();       
    if(httpGet >0)                   
    {      
         BLINKER_LOG(BLINKER_F("[HTTP] GET... success,URI:"), XINZHI_uri    );
         BLINKER_LOG(BLINKER_F("[HTTP] GET... success,httpCode:"), httpGet);   
         String payload="";                                                  
         payload += http.getString();                                       
         http.end();                                                          
         BLINKER_LOG_ALL(BLINKER_F("reply was:"));                             
         BLINKER_LOG_ALL(BLINKER_F("=============================="));         
         BLINKER_LOG_ALL(payload);                                             
         BLINKER_LOG_ALL(BLINKER_F("=============================="));                  
         DynamicJsonDocument jsonBuffer(1024);                                
         DeserializationError error = deserializeJson(jsonBuffer, payload);    
         if (error)weather_success=false;                                      
         else                                                                 
         {
             weather_success=true;                                                 
             JsonObject root = jsonBuffer.as<JsonObject>();                        

             String da=root["results"][0]["now"]["text"];BLINKER_LOG_ALL(da);       
             String co=root["results"][0]["now"]["code"];BLINKER_LOG_ALL(co);       
             String ta=root["results"][0]["now"]["temperature"];BLINKER_LOG_ALL(ta);
             String dm=root["results"][0]["location"]["name"];BLINKER_LOG_ALL(dm);  

             int code=atoi(co.c_str());                                            
             parse_weather_code(code);                                           
             float temp_get=atof(ta.c_str());BLINKER_LOG_ALL(temp_get);           
             float dec=0;                                                       
             if (temp_read-temp_get>=0)dec=temp_read-temp_get;                 
             else dec=temp_get-temp_read;                                     
             weather_text="";weather_text+=dm;weather_text+="市当前天气：";weather_text+=da;weather_text+="，室外温度：";weather_text+=ta;weather_text+="°C";BLINKER_LOG_ALL(weather_text);
             temperature="";temperature+="室内外温差：";temperature+=dec;temperature+="°C";BLINKER_LOG_ALL(temperature);
         }
     }
}
void parse_weather_code(int cod)
{
    switch(cod)
    {
    case 0 :weather_icon="fad fa-sun";break;                
    case 1 :weather_icon="fad fa-moon";break;                
    case 2 :weather_icon="fad fa-sun";break;                 
    case 3 :weather_icon="fad fa-moon";break;                
    case 4 :weather_icon="fad fa-clouds";break;              
    case 5 :weather_icon="fad fa-cloud-sun";break;           
    case 6 :weather_icon="fad fa-cloud-moon";break;         
    case 7 :weather_icon="fad fa-clouds-sun";break;         
    case 8 :weather_icon="fad fa-clouds-moon";break;        
    case 9 :weather_icon="fad fa-cloud";break;              
    case 10:weather_icon="fad fa-cloud-sun-rain";break;     
    case 11:weather_icon="fad fa-thunderstorm";break;        
    case 12:weather_icon="fad fa-cloud-hail-mixed";break;    
    case 13:weather_icon="fad fa-cloud-drizzle";break;      
    case 14:weather_icon="fad fa-cloud-showers";break;       
    case 15:weather_icon="fad fa-cloud-rain";break;         
    case 16:weather_icon="fad fa-cloud-showers-heavy";break;
    case 17:weather_icon="fad fa-cloud-showers-heavy";break;
    case 18:weather_icon="fad fa-cloud-showers-heavy";break; 
    case 19:weather_icon="fad fa-cloud-hail-mixed";break;   
    case 20:weather_icon="fad fa-cloud-sleet";break;       
    case 21:weather_icon="fad fa-snowflake";break;          
    case 22:weather_icon="fad fa-snowflakes";break;        
    case 23:weather_icon="fad fa-cloud-snow";break;          
    case 24:weather_icon="fad fa-cloud-snow";break;          
    case 25:weather_icon="fad fa-cloud-snow";break;          
    case 26:weather_icon="fad fa-sun-dust";break;            
    case 27:weather_icon="fad fa-sun-dust";break;           
    case 28:weather_icon="fad fa-smog";break;               
    case 29:weather_icon="fad fa-smoke";break;              
    case 30:weather_icon="fad fa-fog";break;               
    case 31:weather_icon="fad fa-fog";break;                
    case 32:weather_icon="fad fa-wind";break;              
    case 33:weather_icon="fad fa-wind-warning";break;     
    case 34:weather_icon="fad fa-hurricane";break;        
    case 35:weather_icon="fad fa-hurricane";break;         
    case 36:weather_icon="fad fa-tornado";break;          
    case 37:weather_icon="fad fa-tempreature-hot";break;    
    case 38:weather_icon="fad fa-tempreature-frigid";break; 
    case 99:weather_icon="fad fa-times";break;               
    default:weather_icon="fad fa-times";break;             
    }
}
void DHT11_Read()
{
   
    float h,t,f;                                              
    
    
    #if defined(DHTPIN)
    h = dht.readHumidity(); t = dht.readTemperature();        
    f = dht.readTemperature(true);                         
    #else
        #if defined (APPTEMP)
        h = random(10,30);t =temp_APP;                          
        f = 32+h*1.8;                                          
        #elif defined(RANTEMP)
        h = random(10,30);t = random(10,30);                    
        f = 32+temp_read*1.8;                                   
        #endif
    #endif
    
    if (isnan(t)||isnan(h)|| isnan(f)){BLINKER_LOG("DHT Failed!");return;} 


    temp_read = t;humi_read = h;temp_read_f = f;               
    #if defined(DHTPIN)
    hif = dht.computeHeatIndex(temp_read_f, humi_read);         
    hic = dht.computeHeatIndex(temp_read, humi_read,false);    
    #endif
    BLINKER_LOG("Humidity: "   , h, " %");                      
    BLINKER_LOG("Temperature: ", t, " °C");                   
    BLINKER_LOG("Temperature: ", f, " °F");                     
    BLINKER_LOG("Heat index: ", hic," *C");                    
    BLINKER_LOG("Heat index: ", hif," *F");                     
}
void checkWaterLevel() {
    int adc_new = 0;
    int i;
    for (i = 0; i < 3; i++) {
        adc_new += analogRead(34); // 读取模拟值
        delay(100); // 等待一段时间，以便进行下一次读取
    }
    adc_new = adc_new / 3; // 计算三次读取的平均值

    float water_read = (1.0f - ((float)adc_new / (float)4095)) * 100; // 转换为百分比
    int sensorState = digitalRead(13);
    Serial.print(" | Sensor State: ");
    Serial.println(sensorState); // 打印数字值（0或1）
    if (isnan(water_read)) {
        BLINKER_LOG("Failed to read from water level sensor!");
    } else {
        BLINKER_LOG("Water Level: ", water_read, "%");

        // 根据水位控制LED灯
        if (water_read > 50.0) {
            digitalWrite(13, HIGH);
        } else {
            digitalWrite(13, LOW);
        }

        // 发送水位数据到Blinker
        WATER.print(water_read);
    }
}

void dataStorage()
{   
    if(read_time-stor_time>120000)
    { 
        Blinker.dataStorage("humi_read", humi_read);
        Blinker.dataStorage("temp_read", temp_read);
        Blinker.dataStorage("temp_read_f", temp_read);
        stor_time=read_time;                  
    }
}
#if defined(RELAYPIN_1)
BlinkerButton Button1("b1",button1_callback);
void button1_callback(const String & state)
{
    if(state=="tap"){button1_state=!button1_state;}
    else if(state=="on"){button1_state=true;}      
    else if(state=="off"){button1_state=false;}    
    else if(state=="press"){button1_state=true;}   
    else if(state=="pressup"){button1_state=false;}
    Button1.print(button1_state?"on":"off");
    digitalWrite(RELAYPIN_1,!button1_state);
}
#endif
#if defined(RELAYPIN_2)
BlinkerButton Button2("b2",button2_callback);
void button2_callback(const String & state)
{   
    if(state=="tap"){button2_state=!button2_state;}
    else if(state=="on"){button2_state=true;}      
    else if(state=="off"){button2_state=false;}   
    else if(state=="press"){button2_state=true;}   
    else if(state=="pressup"){button2_state=false;}
    Button2.print(button2_state?"on":"off");
    digitalWrite(RELAYPIN_2,!button2_state);
}
#endif
void heartbeat()
{   
    hebt_time=millis();
}
void my_heartbeat()
{
    if(millis()-hebt_time_limit>5000&&millis()-tap_time>5000)
    {

        if(humi_read==0                      && temp_read==0                ){Text1.print("暂无室内温湿度数据"    ,"异常！");HUMI.color("#FF0000");TEMP.color("#FF0000");TEMPF.color("#FF0000");Text1.color("#FF0000");}        
        else if(humi_read<10                 && temp_read<10                ){Text1.print("室内温度、湿度过低"    ,"异常！");HUMI.color("#0000FF");TEMP.color("#0000FF");TEMPF.color("#0000FF");Text1.color("#FF0000");}
        else if(humi_read>70                 && temp_read>30                ){Text1.print("室内温度、湿度过高"    ,"异常！");HUMI.color("#FF0000");TEMP.color("#FF0000");TEMPF.color("#FF0000");Text1.color("#FF0000");}
        else if(humi_read>70                 && temp_read<30 && temp_read>10){Text1.print("室内湿度过高"         ,"异常！");HUMI.color("#FF0000");Text1.color("#FF0000");}
        else if(humi_read<10                 && temp_read<30 && temp_read>10){Text1.print("室内湿度过低"         ,"异常！");HUMI.color("#0000FF");Text1.color("#FF0000");}                
        else if(humi_read>10 && humi_read<70 && temp_read>30                ){Text1.print("室内温度过高"         ,"异常！");TEMP.color("#FF0000");TEMPF.color("#FF0000");Text1.color("#FF0000");}
        else if(humi_read>10 && humi_read<70 && temp_read<10                ){Text1.print("室内温度过低"         ,"异常！");TEMP.color("#0000FF");TEMPF.color("#0000FF");Text1.color("#FF0000");}
        else if(humi_read>10 && humi_read<70 && temp_read>10 && temp_read<30 ){Text1.print("室内温度湿度均正常"    ,"监测节点运行中……");}

        HUMI.print(humi_read);TEMP.print(temp_read);TEMPF.print(temp_read_f); 
        HIC.print(hic);HIF.print(hif);                                       
        #if defined(RELAYPIN_1)
        button1_callback("");                           
        #endif
        #if defined(RELAYPIN_2)
        button2_callback("");                       
        #endif
        Text2.print("系统已运行"+run_time(),sig_str);Text2.icon(sign_icon);    
        Text3.print(date,tip);Text3.icon("far fa-clock");                    
        Text4.print(weather_text,temperature);Text4.icon(weather_icon);      
        hebt_time_limit=millis();   
    }
}

void dataRead(const String & data)
{
    // 定义中国省份车牌缩写列表
    const char* chinaProvinces[] = {"京", "沪", "津", "渝", "冀", "晋", "蒙", "辽",
                                    "吉", "皖", "闽", "赣", "鲁", "豫", "鄂", "湘", "粤",
                                    "桂", "琼", "川", "贵", "云", "藏", "陕", "甘", "青",
                                    "宁", "新", "港", "澳", "台"};
    size_t provincesCount = sizeof(chinaProvinces) / sizeof(chinaProvinces[0]);

    // 检查是否定义了APPTEMP，如果定义了，尝试将data转换为浮点数
    #if defined(APPTEMP)
    float temp_app = atof(data.c_str());
    if (!isnan(temp_app)) {
        temp_APP = temp_app;
    }
    #endif

    // 判断data是否以中国省份车牌缩写开头并且第二个字符为大写字母
    int flag = 0;
    if (data.length() > 1) {
        const char* cstr = data.c_str();
        String firstChar = String(cstr[0]);
        String secondChar;

        // 判断第一个字符是否是多字节字符
        if ((cstr[0] & 0xE0) == 0xE0) { // 如果第一个字符是多字节字符
            firstChar = String(cstr).substring(0, 3);
            secondChar = String(cstr).substring(3, 4);
        } else {
            secondChar = String(cstr).substring(1, 2);
        }

        Serial.print("First character: ");
        Serial.println(firstChar); // 输出第一个字符用于调试
        Serial.print("Second character: ");
        Serial.println(secondChar); // 输出第二个字符用于调试

        // 检查第一个字符是否在省份车牌缩写列表中
        for (size_t i = 0; i < provincesCount; ++i) {
            if (firstChar == chinaProvinces[i]) {
                // 检查第二个字符是否为大写字母
                if (secondChar.length() == 1 && isupper(secondChar.charAt(0))) {
                    flag = 1; // 找到匹配项，设置为1
                }
                break;
            }
        }
    }

    // 根据flag的值输出相应的信息
    if (flag == 1) {
        Text5.print("车牌录入成功，当前车牌:" + data);
        x=data;
        
    } else {
        Text5.print(data + "为非法录入");Text5.color("#FF0000");
    }
}





#if defined(BUTTONPIN)  
    #define KEY_NO   0  
    #define KEY_DOWN 1 
    #define KEY_UP   2 
    int key_state=0;
    void BUTTON_Read(void)
    {
        #if defined(SELF_LOCK)
            switch(key_state) 
            {   
                case KEY_NO:                    
                    if(!digitalRead(BUTTONPIN)) key_state = KEY_DOWN; 
                break; 
                
                case KEY_DOWN:                  
                    if(!digitalRead(BUTTONPIN) &&millis()-tap_time>10)
                    {   
                        button1_callback("on");
                        tap_time=millis();
                        key_state = KEY_UP;         
                    } 
                    else 
                        key_state = KEY_NO;   
                break;  
                
                case KEY_UP:                  
                    if(digitalRead(BUTTONPIN)) 
                    {
                        button1_callback("off");
                        key_state = KEY_NO; 
                    }
                break;             
            } 
        #else
            switch(key_state) 
            {   
                case KEY_NO:                    
                    if(!digitalRead(BUTTONPIN)) key_state = KEY_DOWN; 
                break; 
                case KEY_DOWN:                
                    if(!digitalRead(BUTTONPIN) &&millis()-tap_time>10)
                    {   
                        button1_callback("tap");
                        tap_time=millis();
                        key_state = KEY_UP;         
                    } 
                    else 
                        key_state = KEY_NO;        
                break;  
                case KEY_UP:                   
                    if(digitalRead(BUTTONPIN)) key_state = KEY_NO; 
                break;             
            } 
            
            //if(!digitalRead(BUTTONPIN) &&millis()-tap_time>500){button1_callback("tap");tap_time=millis();}
        #endif
    }
#endif

void setup() 
{   
    pinMode(13, OUTPUT);
    digitalWrite(13, LOW);
#if defined(RELAYPIN_1)
    //pinMode(RELAYPIN_1,OUTPUT);           
    pinMode(RELAYPIN_1,OUTPUT_OPEN_DRAIN);  
    digitalWrite(RELAYPIN_1,HIGH);         
#endif
#if defined(RELAYPIN_2)
    //pinMode(RELAYPIN_2,OUTPUT);          
    pinMode(RELAYPIN_2,OUTPUT_OPEN_DRAIN);  
    digitalWrite(RELAYPIN_2,HIGH);          
#endif
#if defined(BUTTONPIN)//当定义实体开关，编译以下语句
    pinMode(BUTTONPIN,INPUT_PULLUP);        
#endif
    Serial.begin(115200);                 
    BLINKER_DEBUG.stream(Serial);       
    BLINKER_DEBUG.debugAll();           
    
    Blinker.begin(auth, ssid, pswd);       
    Blinker.attachData(dataRead);
    Blinker.attachHeartbeat(heartbeat);    
    //Blinker.attachSummary(summary);       
    Blinker.attachDataStorage(dataStorage);
#if defined(DHTPIN)
    dht.begin();                           
#endif

}
void loop() 
{
    Blinker.run();
    if(Blinker.connect()) 
    {   
        offline_millis=0;offline_flag=false;
        
        if(millis()-hebt_time<120000)my_heartbeat();
        if(weat_time==0||(millis()-weat_time)>=360000||weather_success==false){weather_read();weat_time=millis();}
        if((millis() - read_time) >= 20000){signal_read();date_read();DHT11_Read();checkWaterLevel();read_time = millis();}
        
    }
    else if(!Blinker.connected()&&millis()>180000)
    {
        offline_flag=true;
    }
    if(millis()>180000&&!offline_millis&&offline_flag)
    {   
        offline_flag=false;
        offline_millis=millis();
    } 
    if(millis()>180000&&offline_millis&&millis()-offline_millis>=180000)
    {
        BLINKER_LOG_ALL(BLINKER_F("******************************************Reatart***************************************"));
        ESP.restart();
    }

#if defined(BUTTONPIN)   
    if(millis()>3000)BUTTON_Read();
#endif
}
